/**
 * =====================================================
 * 🛠️ 《魔性大陆》装备数据爬虫（并发优化版）
 * 功能：自动抓取所有装备数据，生成 JSON + SQL 文件
 * 特性：高并发、限流控制、自动去重、图片下载、属性映射、SQL 生成
 * 作者：AI 助手
 * 时间：2025年
 * =====================================================
 */

const axios = require('axios');                    // HTTP 请求库
const cheerio = require('cheerio');                // HTML 解析库（类似 jQuery）
const fs = require('fs');                          // 文件系统操作
const path = require('path');                      // 路径处理
const https = require('https');                    // HTTPS Agent（复用连接）

// ==================== 🧩 配置区域 ====================
const BASE_URL = 'https://mxdzlk.com';             // 网站根地址
const LIST_URL_TEMPLATE = `${BASE_URL}/item/equip/?equip_type=&weapon_type=&equip_job=&keyword=&page_num=`; // 列表页模板
const IMAGE_DIR = path.join(__dirname, 'images');  // 图片保存目录
const OUTPUT_JSON = path.join(__dirname, 'equip_data.json');     // 原始数据 JSON
const CLEANED_JSON = path.join(__dirname, 'cleaned_data.json');  // 清洗后数据
const FLATTENED_JSON = path.join(__dirname, 'flattened_data.json'); // 展开属性
const OUTPUT_SQL = path.join(__dirname, 'insert_equipments.sql');   // 最终 SQL 文件

// 🚦 并发控制（防止被封或压垮服务器）
const MAX_CONCURRENT_PAGES = 5;        // 同时抓取最多 5 个列表页
const MAX_CONCURRENT_DETAILS = 10;     // 同时抓取最多 10 个详情页

// 📅 抓取页码范围（可调整，建议先小范围测试）
const PAGE_RANGE = { start: 1, end: 268 };

// 🌐 使用 HTTPS Agent 复用 TCP 连接，提升请求速度
const httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 20 });

// 📁 确保图片目录存在
if (!fs.existsSync(IMAGE_DIR)) {
    fs.mkdirSync(IMAGE_DIR, { recursive: true });
    console.log('📁 已创建图片目录:', IMAGE_DIR);
}

// ==================== 🔧 工具函数：并发控制（Promise 池）====================
/**
 * @function throttlePromiseAll
 * @description 控制并发执行的 Promise 任务数，避免请求过多
 * @param {Function[]} tasks - 任务数组，每个是返回 Promise 的函数
 * @param {number} maxConcurrent - 最大并发数
 * @returns {Promise<any[]>} 所有任务结果数组
 */
function throttlePromiseAll(tasks, maxConcurrent) {
    const results = [];
    let index = 0;

    return new Promise((resolve, reject) => {
        if (tasks.length === 0) return resolve(results);

        const spawn = async () => {
            const i = index++;
            if (i >= tasks.length) return;

            try {
                const result = await tasks[i]();      // 执行任务
                results[i] = result;                  // 保持顺序
                spawn();                             // 启动下一个
            } catch (error) {
                reject(error);                       // 任一失败则整体失败
            }
        };

        // 启动初始并发任务
        const concurrent = Math.min(maxConcurrent, tasks.length);
        for (let i = 0; i < concurrent; i++) spawn();

        // 监听完成状态
        const interval = setInterval(() => {
            if (results.length === tasks.length) {
                clearInterval(interval);
                resolve(results);
            }
        }, 10);
    });
}

// ==================== 🕷️ 抓取列表页（获取每页装备基础信息）====================
/**
 * @function fetchListPage
 * @description 抓取指定页码的装备列表，提取基础信息（ID、名称、等级、分类等）
 * @param {number} pageNum - 页码
 * @returns {Promise<Object[]>} 装备基础信息数组
 */
async function fetchListPage(pageNum) {
    const url = `${LIST_URL_TEMPLATE}${pageNum}`;
    console.log(`🔍 正在抓取列表页：第 ${pageNum} 页`);

    try {
        const response = await axios.get(url, {
            headers: {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            },
            httpsAgent,           // 复用连接
            timeout: 10000         // 10秒超时
        });

        const $ = cheerio.load(response.data);
        const $rows = $('table.table-bordered tbody tr');
        const items = [];

        if ($rows.length === 0) {
            console.log(`⚠️ 第 ${pageNum} 页无数据（可能已结束）`);
            return [];
        }

        // 遍历每行，提取装备信息
        $rows.each((_, row) => {
            const $row = $(row);
            const $linkEl = $row.find('td a');
            if (!$linkEl.length) return;

            const detailHref = $linkEl.attr('href');
            const itemIdMatch = detailHref.match(/\/item\/(\d+)\//);
            const id = itemIdMatch ? itemIdMatch[1] : null;
            if (!id) return; // 跳过无效项

            const name = $linkEl.text().trim();
            const level = $row.find('td:nth-child(4)').text().trim();
            const category = $row.find('td:nth-child(5)').text().trim() || '未知分类';
            const job = $row.find('td:nth-child(6)').text().trim() || '未知职业';
            const imgSrc = $row.find('td:first-child img').attr('src');
            const imageUrl = imgSrc
                ? (imgSrc.startsWith('//') ? 'https:' + imgSrc : imgSrc)
                : null;

            items.push({
                id,
                name,
                level,
                category,
                job,
                detailUrl: new URL(detailHref, BASE_URL).href,
                imageUrl
            });
        });

        console.log(`✅ 第 ${pageNum} 页抓取完成，共 ${items.length} 件装备`);
        return items;
    } catch (error) {
        console.error(`❌ 抓取第 ${pageNum} 页失败:`, error.message);
        return []; // 失败返回空数组，不中断整体流程
    }
}

// ==================== 📄 抓取详情页（获取属性、职业要求等）====================
/**
 * @function fetchDetailPage
 * @description 根据装备详情链接抓取详细属性
 * @param {Object} item - 基础装备对象
 * @returns {Object} 包含详情的装备对象
 */
async function fetchDetailPage(item) {
    try {
        const response = await axios.get(item.detailUrl, {
            headers: {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            },
            httpsAgent,
            timeout: 10000
        });

        const $ = cheerio.load(response.data);
        let attributes = {};


        // 提取属性列表（如：攻击力：100）
        $('ul li').each((_, el) => {
            const text = $(el).text();
            const [key, value] = text.split('：').map(s => s.trim());
            if (key && value) attributes[key] = value;
        });

        return {
            ...item,
            attributes
        };
    } catch (error) {
        console.error(`❌ 抓取详情失败 (${item.name}):`, error.message);
        return { ...item }; // 保留基础数据
    }
}

// ==================== 🖼️ 下载图片（异步非阻塞）====================
/**
 * @function downloadImage
 * @description 异步下载图片到本地 images/ 目录
 * @param {string} url - 图片 URL
 * @param {string} filename - 保存文件名（如 123.png）
 */
function downloadImage(url, filename) {
    if (!url || !filename) return;

    const filePath = path.join(IMAGE_DIR, filename);
    const imageUrl = url.startsWith('//') ? 'https:' + url : url;

    https.get(imageUrl, { timeout: 10000 }, (res) => {
        if (res.statusCode !== 200) {
            console.error(`❌ 图片下载失败 (${filename}): HTTP ${res.statusCode}`);
            return;
        }

        const fileStream = fs.createWriteStream(filePath);
        res.pipe(fileStream);

        fileStream.on('finish', () => {
            console.log(`✅ 图片下载成功: ${filename}`);
        });

        fileStream.on('error', (err) => {
            fs.unlink(filePath, () => { }); // 删除残缺文件
            console.error(`❌ 图片写入失败: ${filename}`, err.message);
        });
    }).on('error', (err) => {
        console.error(`❌ 图片请求失败: ${imageUrl}`, err.message);
    });
}

// ==================== 🧹 数据清洗与属性映射 ====================
/**
 * @function cleanAndFlattenData
 * @description 清洗数据，展开属性，映射英文字段，自动拆分如 "1 (0-2)" 类型的复合值
 */
function cleanAndFlattenData(rawData) {
    // 属性中文 → 英文映射表（可扩展）
    const attributeMapping = {
        "HP": "hp", "MP": "mp", "力量": "strength", "敏捷": "agility",
        "智力": "intelligence", "运气": "luck", "防御力": "defense",
        "魔法防御力": "magicDefense", "攻击力": "attackPower",
        "魔法力": "magicPower", "命中率": "hitRate", "回避率": "dodgeRate",
        "攻击速度": "attackSpeed", "移动速度": "moveSpeed",
        "跳跃力": "jumpPower", "可升级次数": "upgradeCount"
    };

    // 收集所有最终可能出现的字段（包括拆分后的 min/max）
    const allFields = new Set();

    // 第一步：扫描所有数据，收集所有可能的字段名（含拆分后）
    rawData.forEach(item => {
        if (item.attributes && typeof item.attributes === 'object') {
            Object.keys(item.attributes).forEach(key => {
                if (key === '装备分类') return;
                const baseKey = attributeMapping[key] || key;
                allFields.add(baseKey); // 基础字段

                const value = item.attributes[key];
                if (typeof value === 'string') {
                    const match = value.match(/^(.+?)\s*\((\d+)-(\d+)\)$/);
                    if (match) {
                        const [, base, min, max] = match;
                        allFields.add(`min_${baseKey}`);
                        allFields.add(`max_${baseKey}`);
                    }
                }
            });
        }
    });

    // 转为排序数组，便于后续补全字段
    const finalFields = Array.from(allFields).sort();

    // 第二步：处理每条数据
    const cleaned = rawData.map(item => {
        const cleanedItem = {
            id: item.id,
            name: item.name,
            level: item.level,
            category: item.category,
            job: item.job,
            upgradeCount: item.upgradeCount || '',
        };

        // 处理属性字段
        if (item.attributes && typeof item.attributes === 'object') {
            Object.keys(item.attributes).forEach(key => {
                if (key === '装备分类') return;
                const baseKey = attributeMapping[key] || key;
                const value = item.attributes[key];

                if (typeof value === 'string') {
                    // 检查是否为 "基础值 (最小-最大)" 模式
                    const match = value.match(/^(.+?)\s*\((\d+)-(\d+)\)$/);
                    if (match) {
                        const [, base, min, max] = match;
                        cleanedItem[baseKey] = base.trim();           // 基础值
                        cleanedItem[`min_${baseKey}`] = min;          // 最小值
                        cleanedItem[`max_${baseKey}`] = max;          // 最大值
                    } else {
                        // 不匹配则直接赋值
                        cleanedItem[baseKey] = value;
                    }
                } else {
                    // 非字符串直接赋值（如数字等）
                    cleanedItem[baseKey] = value;
                }
            });
        }

        // 补全缺失字段（防止 JSON 结构不一致）
        finalFields.forEach(field => {
            if (!(field in cleanedItem)) {
                cleanedItem[field] = '';
            }
        });

        return cleanedItem;
    });

    // 保存清洗后数据
    fs.writeFileSync(CLEANED_JSON, JSON.stringify(cleaned, null, 2), 'utf-8');
    console.log(`🧹 清洗完成，已保存至: ${CLEANED_JSON}`);

    return cleaned;
}

// ==================== 💾 生成 SQL 插入语句 ====================
/**
 * @function generateSQL
 * @description 生成符合 mysqldump 风格的专业 SQL 文件，含字段注释、索引、锁表等
 */
function generateSQL(data) {
    if (!data || data.length === 0) {
        console.log('⚠️ 无数据可生成 SQL');
        return;
    }

    const tableName = 'equipments';
    const tableComment = '装备表：所有可装备道具的基础属性与浮动范围';

    // 所有字段（假设来自 cleaned_data.json 的 key）
    const columns = Object.keys(data[0]).sort();

    // 区分基础字段与 min/max 浮动字段，用于添加备注
    const baseAttributes = columns.filter(col =>
        !col.startsWith('min_') && !col.startsWith('max_')
    );

    const derivedFields = columns.filter(col =>
        col.startsWith('min_') || col.startsWith('max_')
    );

    // ==================== 构建 CREATE TABLE 语句 ====================
    const createLines = [
        `--`,
        `-- Table structure for table \`${tableName}\``,
        `--`,
        '',
        `DROP TABLE IF EXISTS \`${tableName}\`;`,
        `/*!40101 SET @saved_cs_client     = @@character_set_client */;`,
        `/*!40101 SET character_set_client = utf8mb4 */;`,
        `CREATE TABLE \`${tableName}\` (`
    ];

    const fieldDefinitions = [];

    // 主键 id
    fieldDefinitions.push(`  \`id\` varchar(20) NOT NULL COMMENT '装备唯一ID'`);

    // 其他字段
    columns.forEach(col => {
        if (col === 'id') return;

        let dataType = 'varchar(255)';
        let comment = '';

        // 基础字段加通用备注
        if (col === 'name') comment = '装备名称';
        else if (col === 'level') comment = '装备等级';
        else if (col === 'category') comment = '装备分类（武器/防具/饰品等）';
        else if (col === 'job') comment = '适用职业';
        else if (col === 'jobRequirements') comment = '职业要求列表（逗号分隔）';
        else if (baseAttributes.includes(col)) comment = `基础${col}值`;
        else if (col.startsWith('min_')) {
            const base = col.slice(4);
            comment = `最小${base}值（浮动区间）`;
        } else if (col.startsWith('max_')) {
            const base = col.slice(4);
            comment = `最大${base}值（浮动区间）`;
        } else comment = `${col}值`;

        fieldDefinitions.push(`  \`${col}\` ${dataType} DEFAULT NULL COMMENT '${comment}'`);
    });

    // 添加主键和索引
    const keys = [
        `  PRIMARY KEY (\`id\`)`,
        `  KEY \`idx_level\` (\`level\`)`,
        `  KEY \`idx_category\` (\`category\`)`,
        `  KEY \`idx_job\` (\`job\`)`,
    ];

    // 拼接所有字段定义
    createLines.push(fieldDefinitions.join(',\n'));
    createLines.push(',', keys.join(',\n'));
    createLines.push(`) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='${tableComment}';`);
    createLines.push(`/*!40101 SET character_set_client = @saved_cs_client */;`);
    createLines.push('');

    // ==================== 构建 INSERT 语句 ====================
    createLines.push(`--`, `-- Dumping data for table \`${tableName}\``, `--`, '');
    createLines.push(`LOCK TABLES \`${tableName}\` WRITE;`);
    createLines.push(`/*!40000 ALTER TABLE \`${tableName}\` DISABLE KEYS */;`);

    // 生成单行 INSERT 语句
    const columnList = columns.map(col => `\`${col}\``).join(', ');
    const valuesList = data.map(item => {
        const vals = columns.map(col => {
            const val = item[col];
            if (val == null || val === '') return 'NULL';
            return `'${String(val).replace(/'/g, "''")}'`; // 转义单引号
        });
        return `(${vals.join(',')})`;
    }).join(',');

    const insertLine = `INSERT INTO \`${tableName}\` (${columnList}) VALUES ${valuesList};`;
    createLines.push(insertLine);

    // 结束锁表
    createLines.push(`/*!40000 ALTER TABLE \`${tableName}\` ENABLE KEYS */;`);
    createLines.push(`UNLOCK TABLES;`);

    // 写入文件
    fs.writeFileSync(OUTPUT_SQL, createLines.join('\n'), 'utf-8');
    console.log(`💾 专业版 SQL 文件已生成: ${path.resolve(OUTPUT_SQL)}`);
}

// ==================== 🚀 主函数 ====================
/**
 * @function main
 * @description 主流程：抓取 → 清洗 → 生成 SQL
 */
async function main() {
    console.log('🚀 开始执行《魔性大陆》装备数据爬取任务...');

    // 1. 并发抓取所有列表页
    const pageTasks = [];
    for (let i = PAGE_RANGE.start; i <= PAGE_RANGE.end; i++) {
        pageTasks.push(() => fetchListPage(i));
    }

    console.log(`📦 共 ${pageTasks.length} 页待抓取，最大并发 ${MAX_CONCURRENT_PAGES}...`);
    const allPageItems = await throttlePromiseAll(pageTasks, MAX_CONCURRENT_PAGES);
    const allItems = allPageItems.flat();

    if (allItems.length === 0) {
        console.error('❌ 未抓取到任何数据，请检查网络或网址是否正确');
        return;
    }

    console.log(`✅ 列表抓取完成，共 ${allItems.length} 件装备`);

    // 2. 并发抓取详情页
    const detailTasks = allItems.map(item => () => fetchDetailPage(item));
    const detailedItems = await throttlePromiseAll(detailTasks, MAX_CONCURRENT_DETAILS);

    // 3. 去重（按 ID）
    const seen = new Set();
    const uniqueItems = detailedItems.filter(item => {
        if (seen.has(item.id)) return false;
        seen.add(item.id);
        return true;
    });

    // 4. 保存原始数据
    fs.writeFileSync(OUTPUT_JSON, JSON.stringify(uniqueItems, null, 2), 'utf-8');
    console.log(`💾 原始数据已保存: ${OUTPUT_JSON}`);

    // // 5. 异步下载图片（不阻塞主流程）
    // uniqueItems.forEach(item => {
    //     if (item.imageUrl) {
    //         downloadImage(item.imageUrl, `${item.id}.png`);
    //     }
    // });

    // console.log(`🖼️  图片下载任务已启动，共 ${uniqueItems.filter(i => i.imageUrl).length} 张`);

    // 6. 数据清洗与 SQL 生成
    const cleanedData = cleanAndFlattenData(uniqueItems);
    generateSQL(cleanedData);

    console.log('🎉 爬取与处理全部完成！');
}

// ==================== 🏁 启动程序 ====================
main().catch(err => {
    console.error('💥 程序执行出错:', err);
});